package com.dage.salesflow.excel;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.util.MapUtils;
import com.dage.salesflow.constant.ImportMode;
import com.dage.salesflow.constant.YesNoEnum;
import com.dage.salesflow.kit.DbKit;
import com.dage.salesflow.kit.Ret;
import com.dage.salesflow.model.base.BaseModel;
import com.jfinal.kit.Kv;
import io.jboot.db.model.JbootModel;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * EXCEL导入处理类，无对象以Map为单位导入
 *
 * @param <EXCEL>  Excel模型类
 * @param <RECORD> 数据库实体类
 */
public class ExcelMapReader<EXCEL, RECORD extends JbootModel<RECORD>> extends AnalysisEventListener<Map<Integer, String>> implements ExcelReaderChecker<RECORD> {
	/**
	 * 每隔BATCH_COUNT条存储数据库，然后清理list ，方便内存回收
	 */
	private static final int BATCH_COUNT = DbKit.DB_BATCH_COUNT;

	private final Class<EXCEL> excelClass;
	private final Class<RECORD> recordClass;
	private final ImportService<RECORD, EXCEL> importService;
	private final ExcelReader<EXCEL, RECORD> excelReader;
	private final Kv extraData;

	/**
	 * 缓存的数据
	 */
	private List<ImportModel<EXCEL, RECORD>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
	/**
	 * 待更新Record列表
	 */
	private List<RECORD> updateList = ListUtils.newArrayList();
	/**
	 * 待插入Record列表
	 */
	private List<RECORD> insertList = ListUtils.newArrayList();
	private Map<Integer, String> headMap;

	/**
	 * 初始化导入监听处理
	 *
	 * @param excel         ExcelModel类
	 * @param record        数据库实体类
	 * @param importService 导入服务，用于插入和更新校验
	 * @param errors        导入结果
	 * @param update        是否覆盖更新模式
	 * @param mode          导入模式，严格模式下任何警告或错误直接导入失败
	 * @param extraData     额外数据
	 */
	public ExcelMapReader(Class<EXCEL> excel, Class<RECORD> record, ImportService<RECORD, EXCEL> importService, List<Ret> errors, boolean update, ImportMode mode, Kv extraData) {
		excelClass = excel;
		recordClass = record;
		this.importService = importService;
		this.extraData = extraData;
		excelReader = new ExcelReader<>(this, importService, errors, mode, update);
	}

	@Override
	public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
		this.headMap = headMap;
		importService.onSheetStart(context.readSheetHolder().getApproximateTotalRowNumber(), extraData, context);
	}

	/**
	 * 这个每一条数据解析都会来调用
	 *
	 * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
	 * @param context
	 */
	@Override
	public void invoke(Map<Integer, String> data, AnalysisContext context) {
		try {
			Map<String, String> mapData = MapUtils.newHashMap();
			for (Map.Entry<Integer, String> entry : headMap.entrySet()) {
				mapData.put(entry.getValue(), data.get(entry.getKey()));
			}
			//额外参数通过ImportMapModel.ret.data传递
			cachedDataList.add(new ImportMapModel<>(context.readRowHolder().getRowIndex(), mapData, recordClass.newInstance(), Ret.ok(extraData)));
			// 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
			if (cachedDataList.size() >= BATCH_COUNT) {
				excelReader.saveData(cachedDataList, context, updateList, insertList);
				// 存储完成清理 list
				updateList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
				insertList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
				cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
			}
		} catch (Exception e) {
			throw new ExcelAnalysisException(e);
		}
	}

	/**
	 * 所有数据解析完成了 都会来调用
	 *
	 * @param context
	 */
	@Override
	public void doAfterAllAnalysed(AnalysisContext context) {
		// 这里也要保存数据，确保最后遗留的数据也存储到数据库
		try {
			excelReader.saveData(cachedDataList, context, updateList, insertList);
		} catch (Exception e) {
			throw new ExcelAnalysisException(e);
		}
	}

	/**
	 * 插入校验
	 *
	 * @param model
	 * @return
	 */
	public Ret addCheck(RECORD model) throws Exception {
		Ret ret = importService.addCheck(model);
		if (ret.isOk() && model instanceof BaseModel) {
			BaseModel bm = (BaseModel) model;
			bm.setCreateUser(StpUtil.getLoginIdAsInt());
			bm.setCreateTime(new Date());
			if (bm.logicDelete) {
				bm.setIsDeleted(YesNoEnum.NO.getValue());
			}
		}
		return ret;
	}

	/**
	 * 更新校验，默认不校验
	 *
	 * @param model
	 * @return
	 */
	public Ret editCheck(RECORD model) {
		if (model instanceof BaseModel) {
			BaseModel bm = (BaseModel) model;
			bm.setUpdateTime(new Date());
			bm.setUpdateUser(StpUtil.getLoginIdAsInt());
		}
		return Ret.ok();
	}
}
